﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Linq.Expressions;

using Kooboo.IoC;
using Kooboo.Domain;

namespace Kooboo.Commerce.Repositories
{
    public abstract class EntityUpdateCommand<TEntity> : IEntityUpdateCommand<TEntity>
    {
        protected abstract string TableName
        {
            get;
        }

        protected abstract string BuildCondition();

        protected virtual KeyValuePair<string,object> BuildSetMember(string fieldName,object value)
        {
            return new KeyValuePair<string, object>(fieldName, value);
        }

        public IQueryable<TEntity> EntitySet
        {
            get;
            set;
        }

        Dictionary<Expression<Func<Expression>>, object> UpdateFields = new Dictionary<Expression<Func<Expression>>, object>();

        public IEntityUpdateCommand<TEntity> Set<TProperty>(Expression<Func<TEntity, TProperty>> field, TProperty value)
        {
            this.UpdateFields[()=> field] = value;
            return this;
        }

        public void Execute()
        {          
            var sqlCondition = this.BuildCondition();
            
            var len = 0;
            List<string> fields = new List<string>();
            List<object> values = new List<object>();
            foreach (var item in this.UpdateFields)
            {
                var field = (LambdaExpression)item.Key.Compile()();

                var fieldName = string.Empty;

                var invokeExpression = field.Body;

                if (field.NodeType == ExpressionType.Convert)
                {
                    invokeExpression = ((UnaryExpression)field.Body).Operand;

                }
                switch (invokeExpression.NodeType)
                {
                    case ExpressionType.MemberAccess:
                        fieldName = ((MemberExpression)invokeExpression).Member.Name;
                        break;
                    case ExpressionType.Parameter:
                        fieldName = ((ParameterExpression)invokeExpression).Name;
                        break;
                    case ExpressionType.Call:
                        fieldName = ((MethodCallExpression)invokeExpression).Method.Name;
                        break;
                    default:
                        throw new NodeTypeNotSupportException(field.Body.NodeType);
                }


                var member = this.BuildSetMember(fieldName, item.Value);

                fields.Add(member.Key);
                values.Add(member.Value);
            }


            string sqlUpdateBlock = string.Join(", ", fields.Select(i => string.Format("[{0}]={1}", i, "{" + (len++) + "}")).ToArray());

            string commandText = string.Format("UPDATE [{0}] SET {1} WHERE {2}", this.TableName, sqlUpdateBlock, sqlCondition);

            this.ExecuteCommand(commandText, this.UpdateFields.Select(i=>i.Value).ToArray());
        }

        void ExecuteCommand(string command, params object[] parameters)
        {
            var list = new List<object>();
            var listVals = new List<bool>();

            for (int x = 0; x < parameters.Count(); x++)
            {
                if (parameters[x] == null || parameters[x] is System.DBNull)
                {
                    command = command.Replace("{" + x + "}", "NULL");
                    listVals.Add(false);
                }
                else
                {
                    list.Add(parameters[x]);
                    listVals.Add(true);
                }
            }

            int nextId = 0;
            for (int i = 0; i < listVals.Count; i++)
            {
                var isUsed = listVals[i];
                if (!isUsed)
                    continue;
                if (nextId != i)
                    command = command.Replace("{" + i.ToString() + "}", "{" + nextId.ToString() + "}");
                nextId++;
            }

            var context = ContextContainer.Current.Resolve<DataWriterContext>();
            context.ExecuteCommand(command, list.ToArray());
        }

    }
}
